package com.jyniubi.gmail.index.aspect;

import com.alibaba.fastjson.JSON;
import com.jyniubi.gmail.index.annotation.GmailCache;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class GmallCacheAspect {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Resource
    private RedissonClient redissonClient;
    @Autowired
    private RBloomFilter bloomFilter;

    /*@Pointcut("execution(* com.jyniubi.gmail.index.service.*.*(..))")
    public void pointCut(){

    }

    @Before("pointCut()")
    public void before(){
        System.out.println("=============前置通知===================");
    }

    @AfterReturning(value = "pointCut()",returning = "result")
    public void afterReturning(Object result){
        System.out.println("=============返回后通知===================" + result);
    }

    @AfterThrowing(value = "pointCut()",throwing = "ex")
    public void afterThrowing(Exception ex){
        System.out.println("=============异常后通知===================" + ex.getMessage());
    }

    @After("pointCut()")
    public void after(){
        System.out.println("=============最终通知===================");
    }*/

    @Around("@annotation(com.jyniubi.gmail.index.annotation.GmailCache)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        // 获取方法对象签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取目标方法对象
        Method method = signature.getMethod();
        // 获取目标方法对象的注解对象
        GmailCache gmallCache = method.getAnnotation(GmailCache.class);
        // 缓存前缀
        String prefix = gmallCache.prefix();
        // 获取目标方法的参数列表
        Object[] args = joinPoint.getArgs();
        List<Object> argList = Arrays.asList(args);
        String argString = argList.toString();
        // 组装缓存key
        String key = prefix + argString;
        // 先查询缓存，如果缓存命中则直接返回
        String json = this.redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(json)){
            return JSON.parseObject(json,method.getReturnType());
        }
        // 防止缓存穿透，加布隆过滤器
        String argListString = argList.get(0).toString();
        if (!bloomFilter.contains(argListString)){
            return null;
        }
        // 为了防止缓存击穿，加分布式锁
        RLock lock = this.redissonClient.getFairLock(gmallCache.lock() + argString);
        lock.lock();
        try {
            // 如果在这个获取锁的过程中其他请求已经获取到该锁，并将查询数据添加到缓存中，需再次确认缓存中是否存在该数据
            // 先查询缓存，如果缓存命中则直接返回
            json = this.redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotBlank(json)){
                return JSON.parseObject(json, method.getReturnType());
            }
            // 如果缓存没有命中，执行目标方法查询数据库
            Object result = joinPoint.proceed(args);
            // 把查询结果放入缓存，给缓存过期时间加随机值，解决缓存雪崩
            int timeOut = gmallCache.timeout() + new Random().nextInt(gmallCache.random());
            this.redisTemplate.opsForValue().set(key,JSON.toJSONString(result),timeOut, TimeUnit.MINUTES);
            return result;
        } finally {
            lock.unlock();
        }
    }
}
